package cn.hc.tool.exec.service;

import cn.hc.tool.common.util.ClassUtil;
import cn.hc.tool.exec.bean.MethodRes;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author huangchao E-mail:fengquan8866@163.com
 * @version 创建时间：2024/9/24 20:35
 */
@Slf4j
@Service
public class HcScanService {

    public MethodRes getMethodsByPkgsAndFields(List<String> pkgNames, List<String> keys) {
        MethodRes res = new MethodRes();
        List<String> ctlPkgNames = new ArrayList<String >();
        List<String> otherPkgNames = new ArrayList<String>();
        for (String name : pkgNames) {
            if (name.contains("controller") || name.contains("action")) {
                ctlPkgNames.add(name);
            } else {
                otherPkgNames.add(name);
            }
        }
        List<Method> ms = ClassUtil.findMethodInPkgsByAnns(ctlPkgNames, Arrays.asList(getAnnClass("org.springframework.web.bind.annotation.RestController"), Controller.class), RequestMapping.class);
        if (!otherPkgNames.isEmpty()) {
            ms.addAll(ClassUtil.getMethodsInPkgs(otherPkgNames));
        }
        ParameterNameDiscoverer discover = new LocalVariableTableParameterNameDiscoverer();
        for (Method m : ms) {
            log.info("resolve method：{}", m);
            if (hasField(m, keys, discover)) {
                res.addReqMethod(m);
            }
            if (!res.hasReqMethod(m)) {
                Type[] genericParameterTypes = m.getGenericParameterTypes();
                for (Type p : genericParameterTypes) {
                    analyzeReqParam(p, keys, m, res, new HashSet<Type>());
                }
            }
            Type rType = m.getGenericReturnType();
            try {
                analyzeReturnParam(rType, keys, m, res, new HashSet<Type>());
            } catch (IllegalAccessException e) {
                log.error("解析出参出错：{}", m, e);
            }
        }
        return res;
    }

    public Class<? extends Annotation> getAnnClass(String name) {
        try {
            Class c = Class.forName(name);
            if (Annotation.class.isAssignableFrom(c)) {
                return c;
            }
        } catch (ClassNotFoundException e) {
            log.error("class not found: {}", name);
        }
        return null;
    }

    private boolean hasField(Method m, List<String> keys, ParameterNameDiscoverer discover) {
        String[] names = discover.getParameterNames(m);
        if (names != null) {
            for (String name : names) {
                if (contains(name, keys)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 检测入参中含有指定字段的方法
     *
     * @param types 避免循环调用
     */
    private void analyzeReqParam(Type c, List<String> keys, Method m, MethodRes res, HashSet<Type> types) {
        if (c instanceof ParameterizedType) {
            Type[] typesto = ((ParameterizedType) c).getActualTypeArguments();
            for (Type t : typesto) {
                analyzeReqParam(t, keys, m, res, types);
            }
        } else if (c instanceof Class) {
            Class cl = (Class) c;
            if (cl.getName().startsWith("com")) {
                List<Field> fs = ClassUtil.getAllFields(cl);
                for (Field f : fs) {
                    if (res.hasReqMethod(m)) {
                        break;
                    } else if (contains(f.getName(), keys)) {
                        log.info("ok in field: {}.{}", c, f);
                        res.addReqMethod(m);
                        break;
                    } else if (invalid(f.getType())) {
                        continue;
                    } else {
                        if (types.contains(f.getType())) {
                            return;
                        }
                        types.add(f.getType());
                        analyzeReqParam(f.getGenericType(), keys, m, res, types);
                    }
                }
            }
        }
    }

    /**
     * 检测出参中含有指定字段的方法
     *
     * @param c
     * @param keys
     * @param m
     * @param res
     * @param types 避免循环调用
     */
    private void analyzeReturnParam(Type c, List<String> keys, Method m, MethodRes res, HashSet<Type> types) throws IllegalAccessException {
        log.info("==========解析类：{}", c);
        if (c instanceof ParameterizedType) {
            Type[] typesto = ((ParameterizedType) c).getActualTypeArguments();
            for (Type t : typesto) {
                analyzeReturnParam(t, keys, m, res, types);
            }
        } else if (c instanceof Class) {
            Class cl = (Class) c;
            if (Object[].class.isAssignableFrom(cl)) {
                if (types.contains(cl)) {
                    return;
                }
                types.add(cl);
                analyzeReturnParam(cl.getComponentType(), keys, m, res, types);
            } else if (cl.getName().startsWith("com")) {
                List<Field> fs = ClassUtil.getAllFields(cl);
                for (Field f : fs) {
                    if (res.hasReturnMethod(m)) {
                        break;
                    } else if (contains(f.getName(), keys)) {
                        log.info("ok in field: {} {}.{}", m, c, f);
                        res.addReturnMethod(m);
                        break;
                    } else if (invalid(f.getType())) {
                    } else {
                        if (types.contains(f.getType())) {
                            return;
                        }
                        types.add(f.getType());
                        log.info("{}#{} anal class {}#{}", m.getDeclaringClass(), m.getName(), c, f.getName());
                        analyzeReturnParam(f.getGenericType(), keys, m, res, types);
                    }
                }
            } else if (ClassUtil.getFieldValue(cl, "genericInfo") != null) {
                res.addMaybeMethod(m);
                log.info("{}#{} maybe class {}", m.getDeclaringClass(), m.getName(), c);
            } else if (invalid(cl)) {
            } else {
                res.addMaybeMethod(m);
                log.info("{}#{} maybe class {}", m.getDeclaringClass(), m.getName(), c);
            }
        } else {
            log.error("error in analyzeReturnParam: {}, {}", c, m);
        }
    }

    private boolean invalid(Class cl) {
        return cl.isPrimitive() ||
                Enum.class.isAssignableFrom(cl) ||
                (cl.getName().startsWith("java") && !Map.class.isAssignableFrom(cl) && !Collection.class.isAssignableFrom(cl));
    }

    private boolean contains(String name, List<String> keys) {
        for (String k : keys) {
            if (name.contains(k)) {
                return true;
            }
        }
        return false;
    }
}
